// Copyright 2010-2012, Vanya Davidenko.
// Используемая кодировка: UTF-8.

#ifndef LIBV_CHECK_H_
#define LIBV_CHECK_H_

#include <cstdio>
#include <cstdlib>  // exit
#include <stdexcept>
#include <sstream>
#include <libv/define.h>
#include <libv/stdint.h>

#include <assert.h>  // check.h должен предоставлять assert
#include <libv/static_assert.h>


#ifndef LIBV_DISABLE_PRECOND_CHECK
# define LIBV_PRECOND(expr)         libv::Condition<libv::PrecondException>(expr, #expr, "Precondition", "", __FILE__, __LINE__)
# define LIBV_PRECOND_MSG(expr,msg) libv::Condition<libv::PrecondException>(expr, #expr, "Precondition", msg, __FILE__, __LINE__)
#else
# define LIBV_PRECOND(expr)
# define LIBV_PRECOND_MSG(expr,msg)
#endif

//#define PRECOND LIBV_PRECOND
//#define PRECOND_MSG LIBV_PRECOND_MSG


// VERIFY отключать нельзя (!), так как вызовы могут производиться внутри него.
#define LIBV_VERIFY(expr)         libv::Condition<libv::VerifyException>(expr, #expr, "Verify", "", __FILE__, __LINE__)
#define LIBV_VERIFY_MSG(expr,msg) libv::Condition<libv::VerifyException>(expr, #expr, "Verify", msg, __FILE__, __LINE__)

//#define VERIFY LIBV_VERIFY
//#define VERIFY_MSG LIBV_VERIFY_MSG



namespace libv
{

/** Базовое исключение, генерируемое при обнаружении ошибок в проверках. */
LIBV_DEFINE_SIMPLE_EXCEPTION(CheckException);

/** Исключение, генерируемое при ложном условии в макросе VERIFY. */
LIBV_DEFINE_DERIVED_EXCEPTION(VerifyException, CheckException)

/** Исключение, генерируемое при ложном условии в макросе PRECOND. */
LIBV_DEFINE_DERIVED_EXCEPTION(PrecondException, CheckException)



inline void Log(const ::std::string& msg, VerifyException)
{
    // VERIFY используется как неотключаемый в релизе assert.
    // Он чаще всего не обрабатывается и его генерация приводит к завершению
    // приложения. На всякий случай запишем причину в лог.
    ::std::fprintf(stderr, "\nVERIFY failure: %s\n", msg.c_str());
}


inline void Log(const ::std::string&, PrecondException)
{
    // Предусловия с большей вероятностью будут обрабатываться и выводить в лог
    // ошибку незачем.
}



/** Проверка условия и
  *   - вывод сообщения об ошибке и завершение (exit) программы (в дебаге)
  *   - генерация исключения TCheckException с методом what() (в релизе)
  * в случае его ложности.
  * @param[in] cond проверяемое условие
  * @param[in] expr строка с условие (полученная препроцессором)
  * @param[in] check название проверки
  * @param[in] msg опциональное пояснительное сообщение
  * @param[in] module_name имя модуля, в котором осуществляется проверка
  * @param[in] module_line соответствующий номер строки в модуле.
  * @exception не в отладочном режиме (релиз): TCheckException
  *            (шаблон класса, унаследованного от libv::CheckException). */
template<class TCheckException>
inline void Condition(const bool cond,
                      const char* const expr,
                      const char* const check,
                      const char* const msg,
                      const char* const module_name,
                      const long module_line)
{
    if ( cond ) {
        return;
    }

    ::std::stringstream exception_msg;
    exception_msg << check << " failure: "
                  << expr << ", "
                  << msg << " @ "
                  << module_name << ":" << module_line;
    const ::std::string ex_msg = exception_msg.str();
    Log(ex_msg, TCheckException(ex_msg));
    throw TCheckException(ex_msg);
}



/** Завершение программы из-за достижения некорректной точки выполнения. */
inline void Die(const char* msg = "")
{
    ::std::fprintf(stderr, "libv::Die called: %s\n", msg);
    ::std::exit(-666);
}

}  // libv

#endif  // LIBV_CHECK_H_
